using System;
using System.Collections.Generic;
using System.Text;

namespace RapidHDL
{
    public class StateMachine : Component 
    {
        System.Collections.Hashtable htStates;

        string sDefaultState;

        ClockComponent oClock;
        public NodeVector ResetIn;

        public StateMachine(Component poParentComponent, string psName, ClockComponent poClockIn)
            : base(poParentComponent, psName)
        {
            htStates = new System.Collections.Hashtable();
            oClock = poClockIn;
            ResetIn = this.CreateNodeVector("RESET_I", 1);
            sDefaultState = "";
        }

        private void CheckStateExists(string psStateName)
        {
            if (!htStates.ContainsKey(psStateName))
                System.Diagnostics.Debug.Fail("Invalid state named " + psStateName + " in machine " + HirarchicalName);
        }

        public State AddState(string psStateName)
        {
            if (htStates.ContainsKey(psStateName))
                return GetState(psStateName);

            State oState = new State(this, psStateName, htStates.Count);
            htStates.Add(psStateName, oState);
            return oState;
        }

        public State GetState(string psStateName)
        {
            CheckStateExists(psStateName);

            return (State)htStates[psStateName];
        }

        public void SetDefaultState(string psStateName)
        {
            GetState(psStateName).IsUsed = true;
            sDefaultState = psStateName;
        }

        public void StateTransition(string psCurrentState, NodeVector pnvCondition, string psNextState)
        {
            State oCurrentState = GetState(psCurrentState);
            State oNextState = GetState(psNextState);

            if (pnvCondition == null)
            {
                oNextState.TransitionCondition.Or(oCurrentState.StateCurrentNodes);
            }
            else
            {
                oNextState.TransitionCondition.Or(Operators.AND(this,oCurrentState.StateCurrentNodes, pnvCondition));
            }
            oNextState.IsUsed = true;
        }

        public void StateTransition(string psCurrentState, string psNextState)
        {
            StateTransition(psCurrentState, null, psNextState);
        }

        public virtual void DefineStateMachine()
        {
        }

        public override void GenerateStructure()
        {
            DefineStateMachine();

            if (htStates.Count < 1)
                System.Diagnostics.Debug.Fail("No states in the state machine " + HirarchicalName);

            foreach (State oState in htStates.Values)
            {
                if (!oState.IsUsed)
                    System.Diagnostics.Debug.Fail("State not used, named '" + oState.StateName  + "' in " + HirarchicalName);
                oState.CreateConstant(htStates.Count);
            }

            if (sDefaultState == "")
            {
                State oState = (State)htStates[Utility.GetFirstHashtableKey(htStates)];
                SetDefaultState(oState.StateName);
            }

            State oDefaultState = GetState(sDefaultState);

            AssignmentMux amCurrentState = new AssignmentMux(this, "current_state", htStates.Count, oDefaultState.StateConstant.OutputNodes);
            amCurrentState.RegisterOutput(oClock, true,oDefaultState.StateConstant.OutputNodes,ResetIn);
            //amCurrentState.AddAssignment(ResetIn, oDefaultState.StateConstant.OutputNodes);

            foreach (State oState in htStates.Values)
            {
                oState.StateCurrentNodes.ConnectSubset(amCurrentState.OutputNodes, oState.Code, oState.Code);
                amCurrentState.AddAssignment(oState.TransitionCondition.OutputNodes, oState.StateConstant.OutputNodes);
            }
        }

        public NodeVector StateCurrent(string psStateName)
        {
            State oState = GetState(psStateName);
            return oState.StateCurrentNodes;
        }

        protected override Component TestBuildStructure(Component poTopComponent, string psTestName)
        {
            
            NodeVector nvSelA = poTopComponent.CreateNodeVector("sel_a", 1,NodeFlowType.Source);
            NodeVector nvSelB = poTopComponent.CreateNodeVector("sel_b", 1, NodeFlowType.Source);
            NodeVector nvReset = poTopComponent.CreateNodeVector("reset", 1, NodeFlowType.Source);
            
            TopLevelComponent oTop = (TopLevelComponent)poTopComponent;
            oTop.AddMainClock(1);

            StateMachine oM = new StateMachine(poTopComponent, "m", oTop.MainClock);
            oM.ResetIn.Connection = nvReset;

            oM.AddState("state_a");
            oM.AddState("state_b");
            oM.AddState("state_c");
            oM.AddState("state_d");
            oM.AddState("state_e");

            oM.SetDefaultState("state_a");

            oM.StateTransition("state_a", nvSelA, "state_b");
            oM.StateTransition("state_b", "state_c");
            oM.StateTransition("state_c", nvSelB, "state_d");
            oM.StateTransition("state_c", Operators.NOT(oM,nvSelB), "state_e");
            oM.StateTransition("state_d", "state_a");
            oM.StateTransition("state_e", "state_b");
            return oM;
        }



        protected override string TestComponent(Clock poClock, Component poTestComponent, string psTestName)
        {
            StateMachine oM = (StateMachine)poTestComponent;

            oM.ParentComponent.SimulationDestabilizeComponent();
            oM.ParentComponent.SimulationStabilizeComponent();
            
            NodeVector nvSelA = poTestComponent.ParentComponent.GetNodeVector(".sel_a");
            NodeVector nvSelB = poTestComponent.ParentComponent.GetNodeVector(".sel_b");
            NodeVector reset = poTestComponent.ParentComponent.GetNodeVector(".reset");

            AssignmentMux oaMux = (AssignmentMux)oM.GetComponent("current_state");
            Condition oC = (Condition)oM.GetComponent("next_S_state_d");

            State sA = oM.GetState("state_a");
            State sB = oM.GetState("state_b");
            State sC = oM.GetState("state_c");
            State sD = oM.GetState("state_d");
            State sE = oM.GetState("state_e");

            //string sIn = "100";
            //string sOut = "00001";
            string sState = "x";
            
            reset.SimulatedNodeVectorAsString = "1"; 
            nvSelA.SimulatedNodeVectorAsString = "0";
            nvSelB.SimulatedNodeVectorAsString = "0";
            poClock.DoClock(4);

            oM.DebugDump(0);

            sState = oaMux.OutputNodes.ToString();    
            Console.WriteLine("CurrentState " + sState);
            if (sA.StateCurrentNodes.ToString() != "1")
                return "failed reset - " + sState;

            reset.SimulatedNodeVectorAsString = "0";
            nvSelB.SimulatedNodeVectorAsString = "1";
            poClock.DoClock(10);
            sState = oaMux.OutputNodes.ToString();
            Console.WriteLine("CurrentState " + sState);
            if (sA.StateCurrentNodes.ToString() != "1")
                return "failed stay state A - " + sState;

            oM.DebugDump(0);

            nvSelA.SimulatedNodeVectorAsString = "1";

            oM.DebugDump(0);


            poClock.DoClock(2);
            sState = oaMux.OutputNodes.ToString();
            Console.WriteLine("CurrentState " + sState);
            if (sB.StateCurrentNodes.ToString() != "1")
                return "failed move B - " + sState;

            oM.DebugDump(0);

            poClock.DoClock(2);
            sState = oaMux.OutputNodes.ToString();
            Console.WriteLine("CurrentState " + sState);
            if (sC.StateCurrentNodes.ToString() != "1")
                return "failed move C - " + sState;

            oM.DebugDump(0);
            
            
            poClock.DoClock(2);
            sState = oaMux.OutputNodes.ToString();
            Console.WriteLine("CurrentState " + sState);
            
            if (sD.StateCurrentNodes.ToString() != "1")
                return "failed move D - " + sState;

            poClock.DoClock(2);
            sState = oaMux.OutputNodes.ToString();
            Console.WriteLine("CurrentState " + sState);
            if (sA.StateCurrentNodes.ToString() != "1")
                return "failed move A - " + sState;

            nvSelB.SimulatedNodeVectorAsString = "0";
            poClock.DoClock(2);
            sState = oaMux.OutputNodes.ToString();
            Console.WriteLine("CurrentState " + sState);
            if (sB.StateCurrentNodes.ToString() != "1")
                return "failed move B - " + sState;

            nvSelA.SimulatedNodeVectorAsString = "0";
            poClock.DoClock(2);
            sState = oaMux.OutputNodes.ToString();
            Console.WriteLine("CurrentState " + sState);
            if (sC.StateCurrentNodes.ToString() != "1")
                return "failed move C - " + sState;

            nvSelA.SimulatedNodeVectorAsString = "0";
            poClock.DoClock(2);
            sState = oaMux.OutputNodes.ToString();
            Console.WriteLine("CurrentState " + sState);
            if (sE.StateCurrentNodes.ToString() != "1")
                return "failed move E - " + sState;

            poClock.DoClock(2);
            sState = oaMux.OutputNodes.ToString();
            Console.WriteLine("CurrentState " + sState);
            if (sB.StateCurrentNodes.ToString() != "1")
                return "failed move B - " + sState;

            


            return ""; 
        }
        

    }
}
